if (isNaN(value))
// Avoid spreading NaNs around
throw TypeError('The slider value must be a number');
- this._displayValue = this._value = Math.max(Math.min(value, 1), 0);
+ this._value = Math.max(Math.min(value, 1), 0);
this._slider = new St.DrawingArea({ style_class: 'popup-slider-menu-item', reactive: true });
this.actor.set_child(this._slider);
if (isNaN(value))
throw TypeError('The slider value must be a number');
- this._displayValue = this._value = Math.max(Math.min(value, 1), 0);
+ this._value = Math.max(Math.min(value, 1), 0);
this._slider.queue_repaint();
},
cr.stroke();
let handleY = height / 2;
- let handleX = handleRadius + (width - 2 * handleRadius) * this._displayValue;
+ let handleX = handleRadius + (width - 2 * handleRadius) * this._value;
let color = new Clutter.Color();
themeNode.get_foreground_color(color);
Clutter.ungrab_pointer();
this._dragging = false;
- this._value = this._displayValue;
- this.emit('value-changed', this._value);
+ this.emit('drag-end');
}
return true;
},
newvalue = 1;
else
newvalue = (relX - handleRadius) / (width - 2 * handleRadius);
- this._displayValue = newvalue;
+ this._value = newvalue;
this._slider.queue_repaint();
+ this.emit('value-changed', this._value);
},
get value() {
let key = event.get_key_symbol();
if (key == Clutter.Right || key == Clutter.Left) {
let delta = key == Clutter.Right ? 0.1 : -0.1;
- this._value = this._displayValue = Math.max(0, Math.min(this._value + delta, 1));
+ this._value = Math.max(0, Math.min(this._value + delta, 1));
this._slider.queue_repaint();
this.emit('value-changed', this._value);
+ this.emit('drag-end');
return true;
}
return false;
--- /dev/null
+/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
+
+const DBus = imports.dbus;
+const Lang = imports.lang;
+const Mainloop = imports.mainloop;
+const Shell = imports.gi.Shell;
+const Gvc = imports.gi.Gvc;
+const Signals = imports.signals;
+const St = imports.gi.St;
+
+const PanelMenu = imports.ui.panelMenu;
+const PopupMenu = imports.ui.popupMenu;
+
+const Gettext = imports.gettext.domain('gnome-shell');
+const _ = Gettext.gettext;
+
+const VOLUME_MAX = 65536.0; /* PA_VOLUME_NORM */
+
+function Indicator() {
+ this._init.apply(this, arguments);
+}
+
+Indicator.prototype = {
+ __proto__: PanelMenu.SystemStatusButton.prototype,
+
+ _init: function() {
+ PanelMenu.SystemStatusButton.prototype._init.call(this, 'audio-volume-muted', null);
+
+ this._control = new Gvc.MixerControl({ name: 'GNOME Shell Volume Control' });
+ this._control.connect('ready', Lang.bind(this, this._onControlReady));
+ this._control.connect('default-sink-changed', Lang.bind(this, this._readOutput));
+ this._control.connect('default-source-changed', Lang.bind(this, this._readInput));
+ this._control.connect('stream-added', Lang.bind(this, this._maybeShowInput));
+ this._control.connect('stream-removed', Lang.bind(this, this._maybeShowInput));
+
+ this._output = null;
+ this._outputVolumeId = 0;
+ this._outputMutedId = 0;
+ this._outputSwitch = new PopupMenu.PopupSwitchMenuItem(_("Output: Muted"), false);
+ this._outputSwitch.connect('toggled', Lang.bind(this, this._switchToggled, '_output'));
+ this._outputSlider = new PopupMenu.PopupSliderMenuItem(0);
+ this._outputSlider.connect('value-changed', Lang.bind(this, this._sliderChanged, '_output'));
+ this._outputSlider.connect('drag-end', Lang.bind(this, this._notifyVolumeChange));
+ this.menu.addMenuItem(this._outputSwitch);
+ this.menu.addMenuItem(this._outputSlider);
+
+ this._separator = new PopupMenu.PopupSeparatorMenuItem();
+ this.menu.addMenuItem(this._separator);
+
+ this._input = null;
+ this._inputVolumeId = 0;
+ this._inputMutedId = 0;
+ this._inputSwitch = new PopupMenu.PopupSwitchMenuItem(_("Input: Muted"), false);
+ this._inputSwitch.connect('toggled', Lang.bind(this, this._switchToggled, '_input'));
+ this._inputSlider = new PopupMenu.PopupSliderMenuItem(0);
+ this._inputSlider.connect('value-changed', Lang.bind(this, this._sliderChanged, '_input'));
+ this._inputSlider.connect('drag-end', Lang.bind(this, this._notifyVolumeChange));
+ this.menu.addMenuItem(this._inputSwitch);
+ this.menu.addMenuItem(this._inputSlider);
+
+ this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
+ this.menu.addAction(_("Sound Preferences"), function() {
+ let p = new Shell.Process({ args: ['gnome-control-center', 'volume'] });
+ p.run();
+ });
+
+ this._control.open();
+ },
+
+ _onControlReady: function() {
+ this._readOutput();
+ this._readInput();
+ },
+
+ _readOutput: function() {
+ if (this._outputVolumeId) {
+ this._output.disconnect(this._outputVolumeId);
+ this._output.disconnect(this._outputMutedId);
+ this._outputVolumeId = 0;
+ this._outputMutedId = 0;
+ }
+ this._output = this._control.get_default_sink();
+ if (this._output) {
+ this._outputMutedId = this._output.connect('notify::is-muted', Lang.bind(this, this._mutedChanged, '_output'));
+ this._outputVolumeId = this._output.connect('notify::volume', Lang.bind(this, this._volumeChanged, '_output'));
+ this._mutedChanged (null, null, '_output');
+ this._volumeChanged (null, null, '_output');
+ this.setIcon(this._volumeToIcon(this._output.volume));
+ } else {
+ this._outputSwitch.label.text = _("Output: Muted");
+ this._outputSwitch.setToggleState(false);
+ this.setIcon('audio-volume-muted-symbolic');
+ }
+ },
+
+ _readInput: function() {
+ if (this._inputVolumeId) {
+ this._input.disconnect(this._inputVolumeId);
+ this._input.disconnect(this._inputMutedId);
+ this._inputVolumeId = 0;
+ this._inputMutedId = 0;
+ }
+ this._input = this._control.get_default_source();
+ if (this._input) {
+ this._inputMutedId = this._input.connect('notify::is-muted', Lang.bind(this, this._mutedChanged, '_input'));
+ this._inputVolumeId = this._input.connect('notify::volume', Lang.bind(this, this._volumeChanged, '_input'));
+ this._mutedChanged (null, null, '_input');
+ this._volumeChanged (null, null, '_input');
+ } else {
+ this._separator.actor.hide();
+ this._inputSwitch.actor.hide();
+ this._inputSlider.actor.hide();
+ }
+ },
+
+ _maybeShowInput: function() {
+ // only show input widgets if any application is recording audio
+ let showInput = false;
+ let recordingApps = this._control.get_source_outputs();
+ if (this._source && recordingApps) {
+ for (let i = 0; i < recordingApp.length; i++) {
+ let outputStream = recordingApp[i];
+ let id = outputStream.get_application_id();
+ // but skip gnome-volume-control and pavucontrol
+ // (that appear as recording because they show the input level)
+ if (!id || (id != 'org.gnome.VolumeControl' && id != 'org.PulseAudio.pavucontrol')) {
+ showInput = true;
+ break;
+ }
+ }
+ }
+ if (showInput) {
+ this._separator.actor.show();
+ this._inputSwitch.actor.show();
+ this._inputSlider.actor.show();
+ } else {
+ this._separator.actor.hide();
+ this._inputSwitch.actor.hide();
+ this._inputSlider.actor.hide();
+ }
+ },
+
+ _volumeToIcon: function(volume) {
+ if (volume <= 0) {
+ return 'audio-volume-muted';
+ } else {
+ let v = volume / VOLUME_MAX;
+ if (v < 0.33)
+ return 'audio-volume-low';
+ if (v > 0.8)
+ return 'audio-volume-high';
+ return 'audio-volume-medium';
+ }
+ },
+
+ _sliderChanged: function(slider, value, property) {
+ if (this[property] == null) {
+ log ('Volume slider changed for %s, but %s does not exist'.format(property, property));
+ return;
+ }
+ this[property].volume = value * VOLUME_MAX;
+ this[property].push_volume();
+ },
+
+ _notifyVolumeChange: function() {
+ global.play_theme_sound('audio-volume-change');
+ },
+
+ _switchToggled: function(switchItem, state, property) {
+ if (this[property] == null) {
+ log ('Volume mute switch toggled for %s, but %s does not exist'.format(property, property));
+ return;
+ }
+ this[property].change_is_muted(!state);
+ this._notifyVolumeChange();
+ },
+
+ _mutedChanged: function(object, param_spec, property) {
+ let muted = this[property].is_muted;
+ let toggleSwitch = this[property+'Switch'];
+ toggleSwitch.setToggleState(!muted);
+ this._updateLabel(property);
+ if (property == '_output') {
+ if (muted)
+ this.setIcon('audio-volume-muted');
+ else
+ this.setIcon(this._volumeToIcon(this._output.volume));
+ }
+ },
+
+ _volumeChanged: function(object, param_spec, property) {
+ this[property+'Slider'].setValue(this[property].volume / VOLUME_MAX);
+ this._updateLabel(property);
+ if (property == '_output')
+ this.setIcon(this._volumeToIcon(this._output.volume));
+ },
+
+ _updateLabel: function(property) {
+ let label;
+ if (this[property].is_muted)
+ label = (property == '_output' ? _("Output: Muted") : _("Input: Muted"));
+ else
+ label = (property == '_output' ? _("Output: %3.0f%%") : _("Input: %3.0f%%")).format(this[property].volume / VOLUME_MAX * 100);
+ this[property+'Switch'].label.text = label;
+ }
+};